You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

326 lines
8.2 KiB

import { beforeEach, describe, expect, mock, test } from "bun:test";
const ownerRecord = {
publicSlug: "demo-user",
nickname: "Demo",
avatar: "https://cdn.example.com/avatar.png",
avatarVisibility: "public",
bioVisibility: "public",
bioMarkdown: "hello bio",
socialLinksJson: "[]",
status: "active",
};
const selectMock = mock(() => ({
from: () => ({
where: () => ({
limit: async () => [ownerRecord],
}),
}),
}));
const getPublicHubBySlug = mock(async () => ({
modules: {
posts: {
items: [
{ slug: "hub-p1", title: "hub-post-1" },
{ slug: "hub-p2", title: "hub-post-2" },
],
total: 6,
},
timeline: {
items: [
{ id: 101, title: "hub-t1" },
{ id: 102, title: "hub-t2" },
],
total: 8,
},
reading: {
items: [
{ id: 2, title: "r1" },
{ id: 3, title: "r2" },
],
total: 6,
},
},
posts: {
items: [
{ slug: "legacy-p1", title: "legacy-post-1" },
{ slug: "legacy-p2", title: "legacy-post-2" },
{ slug: "legacy-p3", title: "legacy-post-3" },
{ slug: "legacy-p4", title: "legacy-post-4" },
],
total: 9,
},
timeline: {
items: [
{ id: 201, title: "legacy-t1" },
{ id: 202, title: "legacy-t2" },
{ id: 203, title: "legacy-t3" },
],
total: 11,
},
rssItems: {
items: [
{ id: 2, title: "r1" },
{ id: 3, title: "r2" },
{ id: 4, title: "r3" },
{ id: 5, title: "r4" },
{ id: 6, title: "r5" },
],
total: 6,
},
}));
const parseSocialLinksJson = mock(() => [
{ label: "x", href: "https://x.com/demo", visibility: "public" },
{ label: "mail", href: "mailto:demo@example.com", visibility: "private" },
]);
mock.module("drizzle-pkg/lib/db", () => ({
dbGlobal: {
select: selectMock,
},
}));
mock.module("drizzle-pkg/lib/schema/auth", () => ({
users: {
publicSlug: "publicSlug",
status: "status",
},
}));
mock.module("drizzle-orm", () => ({
and: (...conditions: unknown[]) => conditions,
eq: (a: unknown, b: unknown) => [a, b],
}));
mock.module("#server/service/profile", () => ({
parseSocialLinksJson,
}));
mock.module("#server/service/public-hub", () => ({
getPublicHubBySlug,
}));
globalThis.defineEventHandler ??= ((handler: unknown) => handler) as never;
globalThis.createError ??= ((input: { statusCode: number; statusMessage: string }) => {
const error = new Error(input.statusMessage) as Error & {
statusCode: number;
statusMessage: string;
};
error.statusCode = input.statusCode;
error.statusMessage = input.statusMessage;
return error;
}) as never;
globalThis.R ??= {
success: (data: unknown) => data,
};
const handler = (await import("./[publicSlug].get")).default;
describe("GET /api/public/profile/:publicSlug", () => {
beforeEach(() => {
ownerRecord.publicSlug = "demo-user";
ownerRecord.nickname = "Demo";
ownerRecord.avatar = "https://cdn.example.com/avatar.png";
ownerRecord.avatarVisibility = "public";
ownerRecord.bioVisibility = "public";
ownerRecord.bioMarkdown = "hello bio";
ownerRecord.socialLinksJson = "[]";
ownerRecord.status = "active";
selectMock.mockClear();
getPublicHubBySlug.mockClear();
parseSocialLinksJson.mockClear();
});
test("通过 public-hub 一次调用返回 modules 与 legacy 字段", async () => {
const result = await handler({
context: {
params: {
publicSlug: "demo-user",
},
},
} as never);
expect(getPublicHubBySlug).toHaveBeenCalledWith("demo-user");
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
expect(selectMock).toHaveBeenCalledTimes(1);
expect(result).toEqual({
user: {
publicSlug: "demo-user",
nickname: "Demo",
avatar: "https://cdn.example.com/avatar.png",
},
bio: {
markdown: "hello bio",
},
links: [{ label: "x", href: "https://x.com/demo", visibility: "public" }],
modules: {
posts: {
items: [
{ slug: "hub-p1", title: "hub-post-1" },
{ slug: "hub-p2", title: "hub-post-2" },
],
total: 6,
},
timeline: {
items: [
{ id: 101, title: "hub-t1" },
{ id: 102, title: "hub-t2" },
],
total: 8,
},
reading: {
items: [
{ id: 2, title: "r1" },
{ id: 3, title: "r2" },
],
total: 6,
},
},
posts: {
items: [
{ slug: "legacy-p1", title: "legacy-post-1" },
{ slug: "legacy-p2", title: "legacy-post-2" },
{ slug: "legacy-p3", title: "legacy-post-3" },
{ slug: "legacy-p4", title: "legacy-post-4" },
],
total: 9,
},
timeline: {
items: [
{ id: 201, title: "legacy-t1" },
{ id: 202, title: "legacy-t2" },
{ id: 203, title: "legacy-t3" },
],
total: 11,
},
rssItems: {
items: [
{ id: 2, title: "r1" },
{ id: 3, title: "r2" },
{ id: 4, title: "r3" },
{ id: 5, title: "r4" },
{ id: 6, title: "r5" },
],
total: 6,
},
});
});
test("publicSlug 缺失时返回 400,且不触发下游 service", async () => {
await expect(
handler({
context: {
params: {},
},
} as never),
).rejects.toMatchObject({
statusCode: 400,
statusMessage: "无效主页",
});
expect(selectMock).toHaveBeenCalledTimes(0);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
});
test("publicSlug 非法时返回 400,且不触发下游 service", async () => {
await expect(
handler({
context: {
params: {
publicSlug: 123,
},
},
} as never),
).rejects.toMatchObject({
statusCode: 400,
statusMessage: "无效主页",
});
expect(selectMock).toHaveBeenCalledTimes(0);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
});
test("owner 不存在或非 active 时返回 404", async () => {
selectMock.mockImplementationOnce(() => ({
from: () => ({
where: () => ({
limit: async () => [],
}),
}),
}));
await expect(
handler({
context: {
params: {
publicSlug: "demo-user",
},
},
} as never),
).rejects.toMatchObject({
statusCode: 404,
statusMessage: "未找到",
});
expect(selectMock).toHaveBeenCalledTimes(1);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(0);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(0);
});
test("avatarVisibility 非 public 时 avatar 为 null", async () => {
ownerRecord.avatarVisibility = "private";
const result = await handler({
context: {
params: {
publicSlug: "demo-user",
},
},
} as never);
expect(result.user.avatar).toBeNull();
expect(selectMock).toHaveBeenCalledTimes(1);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
});
test("bioVisibility 非 public 时 bio 为 null", async () => {
ownerRecord.bioVisibility = "private";
const result = await handler({
context: {
params: {
publicSlug: "demo-user",
},
},
} as never);
expect(result.bio).toBeNull();
expect(selectMock).toHaveBeenCalledTimes(1);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
});
test("无 bioMarkdown 时 bio 为 null", async () => {
ownerRecord.bioMarkdown = "";
const result = await handler({
context: {
params: {
publicSlug: "demo-user",
},
},
} as never);
expect(result.bio).toBeNull();
expect(selectMock).toHaveBeenCalledTimes(1);
expect(parseSocialLinksJson).toHaveBeenCalledTimes(1);
expect(getPublicHubBySlug).toHaveBeenCalledTimes(1);
});
});